package com.holystone.splunk.app.controller;

import com.github.pagehelper.PageInfo;
import com.holystone.splunk.app.common.AlertRunnable;
import com.holystone.splunk.app.enums.AlertEnums;
import com.holystone.splunk.app.model.AlertDto;
import com.holystone.splunk.app.model.AlertVO;
import com.holystone.splunk.app.model.EventVO;
import com.holystone.splunk.app.mybatis.model.Alert;
import com.holystone.splunk.app.mybatis.model.Event;
import com.holystone.splunk.app.service.IIndexAlertService;
import com.holystone.splunk.app.service.IIndexEventService;
import com.holystone.splunk.app.service.ISplunkBaseService;
import com.holystone.splunk.app.utils.AlertUtils;
import com.holystone.splunk.common.JSONMapper;
import com.holystone.splunk.common.PageData;
import com.holystone.splunk.common.SystemConstant;
import com.holystone.splunk.utils.DateFormatUtils;
import com.holystone.splunk.utils.JSONUtils;
import org.apache.log4j.Logger;
import org.codehaus.jackson.JsonNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.*;

/**
 * Copyright (c) 2017, HOLYSTONE Technologies, Inc.
 * All right reserved.
 *
 * @author huyan
 * @date 2017/12/4
 * @desc:
 */
@Controller
@RequestMapping("/splunk/alert")
public class AlertController {

    private Logger logger = Logger.getLogger(this.getClass());
    private final String BASE_URL = "/splunk/alert";
    @Resource(name = "indexAlertService")
    private IIndexAlertService indexAlertService;
    @Resource(name = "indexEventService")
    private IIndexEventService indexEventService;
    @Resource(name = "splunkBaseService")
    private ISplunkBaseService splunkBaseService;
    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;
    @Value("${splunk.address}")
    private String address;
    @Value("${splunk.domain}")
    private String domain;

    private String format= "yyyy-MM-dd";
    private SimpleDateFormat sdf = new SimpleDateFormat(format);

    /**
     * 初始化进入告警管理页面
     * @return
     */
    @RequestMapping("/initAlert")
    public ModelAndView initAlert() {
        logger.debug(">>>>> initAlert() 初始化进入告警列表首页()");
        ModelAndView view = new ModelAndView(BASE_URL + "/alert_list");
        try {
            List<String> nameList = indexAlertService.getExistsAlertNames();
            String enddate = indexAlertService.queryEndTimeAlertData();
            view.addObject("nameList", nameList);
            view.addObject("starttime", sdf.format(new Date(Long.valueOf(enddate+"000")-86400000*2)));
            view.addObject("endtime", sdf.format(new Date(Long.valueOf(enddate+"000"))));
        } catch (Exception e) {
            logger.error("查询告警名称失败：" + e.getMessage(), e);
        }
        logger.debug("<<<<< initAlert() 初始化进入告警列表首页()");
        return view;
    }

    /**
     * 筛选出符合条件的告警信息列表
     * @param pageNo
     * @param pageSize
     * @param conditions
     * @return
     */
    @ResponseBody
    @RequestMapping("/getAlerts")
    public ModelMap getAlerts(String pageNo, String pageSize, AlertVO conditions) {
        logger.debug(">>>>> 获取告警分页 getAlerts() pageNo:" + pageNo + ", pageSize:" + pageSize);
        PageInfo<AlertVO> info = null;
        try {
            info = indexAlertService.getAlertsPageInfo(pageNo, pageSize, conditions);
            List<AlertVO> list = info.getList();
            if (list != null) {
                for (AlertVO vo : list) {
                    vo.setAlertLevelInfo(AlertEnums.AlertLevelEnum.getLevelMsg(vo.getAlertLevel()));
                    vo.setAlertStateInfo(AlertEnums.AlertStateEnum.getStateMsg(vo.getAlertState()));
                    vo.setTriggerTimeStr(DateFormatUtils.date2Str(vo.getTriggerTime()));
                }
            }
        } catch (Exception e) {
            logger.error("获取告警分页出错：" + e.getMessage(), e);
            return JSONMapper.createReturnMap(SystemConstant.RESPONSE_CODE_FAILURE, "查询告警分页出错", null, null);
        }
        logger.debug("<<<<< 获取告警分页 getAlerts() pageNo:" + pageNo + ", pageSize:" + pageSize);
        return PageData.getReturnMap(info);
    }

    @ResponseBody
    @RequestMapping("/getMoreAlerts")
    public ModelMap getMoreAlerts(String pageNo, String pageSize, AlertVO conditions) {
        logger.debug(">>>>> 获取详细的告警分页 getMoreAlerts() pageNo:" + pageNo + ", pageSize:" + pageSize);
        PageInfo<AlertVO> info = null;
        try {
            AlertVO alertVO = indexAlertService.getAlertsBySids(conditions.getAlertSid()).get(0);
            conditions.setR_md5(alertVO.getR_md5());
            info = indexAlertService.getAlertPageInfoByMD5(pageNo, pageSize, conditions);
            List<AlertVO> list = info.getList();
            if (list != null) {
                for (AlertVO vo : list) {
                    vo.setAlertLevelInfo(AlertEnums.AlertLevelEnum.getLevelMsg(vo.getAlertLevel()));
                    vo.setAlertStateInfo(AlertEnums.AlertStateEnum.getStateMsg(vo.getAlertState()));
                    vo.setTriggerTimeStr(DateFormatUtils.date2Str(vo.getTriggerTime()));
                }
            }
        } catch (Exception e) {
            logger.error("获取告警分页出错：" + e.getMessage(), e);
            return JSONMapper.createReturnMap(SystemConstant.RESPONSE_CODE_FAILURE, "查询告警分页出错", null, null);
        }
        logger.debug("<<<<< 获取详细的告警分页 getMoreAlerts() pageNo:" + pageNo + ", pageSize:" + pageSize);
        return PageData.getReturnMap(info);
    }

    /**
     * 初始化进入告警信息详细日志页面
     * @param alertSid
     * @return
     */
    @ResponseBody
    @RequestMapping("/initAlertLogs")
    public ModelMap initAlertLogs(String alertSid) {
        logger.debug(">>>>> 初始化进入日志详情页 initAlertLogs() alertSid:" + alertSid);
        ModelMap model = new ModelMap();
        try {
            Map<String, List<AlertVO>> voMap = indexAlertService.getAlertDataBySid(alertSid);
            // 封装table表格
            List<AlertVO> tableJson = voMap.get("tableJson");
            if (tableJson != null && tableJson.size() != 0) {
                model.addAttribute("tableSource", tableJson.get(0).getAlertName());
                model.addAttribute("tableData", tableJson);
            }
            // 封装原始日志
            List<AlertVO> eventData = voMap.get("eventJson");
            if (eventData != null && eventData.size() != 0) {
                model.addAttribute("eventSource", eventData.get(0).getAlertName()); // 表格
                model.addAttribute("eventData", eventData);
            }
        } catch (Exception e) {
            logger.error("初始化进入页面失败：" + e.getMessage(), e);
            model = JSONMapper.createReturnMap(SystemConstant.RESPONSE_CODE_SUCCESS_NULL_COMTENT, "获取日志信息失败，原因：sid 未知", null, null);
            return model;
        }
        model.addAttribute("code", SystemConstant.RESPONSE_CODE_SUCCESS);
        logger.debug("<<<<< 初始化进入日志详情页 initAlertLogs() alertSid:" + alertSid);
        return model;
    }

    /**
     * 初始化进入添加事件页面
     * @param alertSids
     * @return
     */
    @RequestMapping("/initAddEvent")
    public ModelAndView initAddEvent(String alertSids) {
        logger.debug(">>>>> 初始化进入添加事件页面 initAddEvent() alertSids:" + alertSids);
        ModelAndView view = new ModelAndView(BASE_URL + "/event_add");
        try {
            if(!"".equals(alertSids)){
                List<AlertVO> alertList = indexAlertService.getAlertsBySids(alertSids);
                List<Alert> voList = new ArrayList<>();
                voList.addAll(alertList);
                // 获取的alertvo里没有展示页面需要的属性，需要设置一下
                view.addObject("alertList", AlertVO.getAlertVOs(voList));
//                view.addObject("alertListJson", JSONUtils.javaBean2Json(alertList));
                view.addObject("alertListJson", alertSids);
            }else{
                view.addObject("alertListJson", "");
            }
        } catch (Exception e) {
            logger.error("获取告警列表失败：" + e.getMessage(), e);
        }
        logger.debug("<<<<< 初始化进入添加事件页面 initAddEvent() alertSids:" + alertSids);
        return view;
    }

    /**
     * 保存事件信息
     *
     * @param eventStr
     * @param alertsStr
     * @return
     */
    @ResponseBody
    @RequestMapping("/saveEventAndAlerts")
    public ModelMap saveEvent(String eventStr, String alertsStr,String alertSids) {
        logger.debug(">>>>> 保存事件及其相关的告警信息 saveEventAndAlerts()");
        try {
            EventVO event = JSONUtils.jsonStr2JavaBean(eventStr, EventVO.class);
            Set<AlertVO> modelList=null;
            if(!"".equals(alertSids)){
//                modelList = JSONUtils.jsonStr2Set(alertsStr, AlertDto.class);
                List<AlertVO> alertList = indexAlertService.getAlertsBySids(alertSids);
                if(alertList!=null && alertList.size()>0){
                    modelList = new HashSet<>();
                    for(AlertVO alertVO:alertList){
                        modelList.add(alertVO);
                    }
                }
            }
            if (event.getEventId() == null) {
                indexEventService.saveEventAndAlerts(event, modelList);
            }
        } catch (Exception e) {
            logger.debug("保存事件及告警信息出错：" + e.getMessage(), e);
            return JSONMapper.createReturnMap(SystemConstant.RESPONSE_CODE_FAILURE, "", null, null);
        }
        logger.debug("<<<<< 保存事件及其相关的告警信息 saveEventAndAlerts()");
        return JSONMapper.createReturnMap(SystemConstant.RESPONSE_CODE_SUCCESS, "", null, null);
    }

    /**
     * 初始化进入告警-事件查看页面
     * @param alertSid
     * @return
     */
    @RequestMapping("/initAlertEvents")
    public ModelAndView initAlertEvents(String alertSid) {
        logger.debug(">>>>> 初始化进入告警关联事件表页面 initAlertEvents() alertSid:" + alertSid);
        ModelAndView view = new ModelAndView(BASE_URL + "/alert_events");
        try {
            AlertVO alert = indexAlertService.getAlertsBySids(alertSid).get(0);
            view.addObject("alert", AlertVO.getAlertVO(alert));
            view.addObject("alertSid", alertSid);
        } catch (Exception e) {
            logger.error("初始化进入告警关联事件表页面失败:" + e.getMessage(), e);
        }
        logger.debug("<<<<< 初始化进入告警关联事件表页面 initAlertEvents() alertSid:" + alertSid);
        return view;
    }

    /**
     * 通过告警sid获取与之关联的事件信息列表
     * @param pageNo
     * @param pageSize
     * @param alertSid
     * @return
     */
    @ResponseBody
    @RequestMapping("/getEventsBySid")
    public ModelMap getEventsBySid(String pageNo, String pageSize, String alertSid) {
        logger.debug(">>>>> 通过sid获取事件 getEventsBySid() alertSid:" + alertSid);
        List<EventVO> eventList = new ArrayList<>();
        try {
            AlertVO alert = indexAlertService.getAlertsBySids(alertSid).get(0);
            if (alert.getEvents() != null) {
                for (Event event : alert.getEvents()) {
                    eventList.add(EventVO.getEventVO(event));
                }
            }
        } catch (Exception e) {
            logger.error("获取事件出错：" + e.getMessage(), e);
            return JSONMapper.createReturnMap(SystemConstant.RESPONSE_CODE_FAILURE, "获取事件列表页面出错", null, null);
        }
        logger.debug("<<<<< 通过sid获取事件 getEventsBySid() alertSid:" + alertSid);
        return PageData.getReturnMap(eventList);
    }

    /**
     * 将所有信息存入splunk中之后，无法提供删除操作，此方法暂时舍弃
     * 告警-事件解绑
     * @param alertSid 目标告警信息sid
     * @param eventIds 要解绑的事件ids字符串，以 "," 分割
     * @return
     */
    @ResponseBody
    @RequestMapping("/unLinkEvents")
    public ModelMap unLinkEvents(String alertSid, String eventIds) {
        logger.debug(">>>>> 解绑事件 unLinkEvents() alertSid:" + alertSid + ", eventIds:" + eventIds);
        try {
            if (eventIds != null && eventIds != "") {
                indexAlertService.unLinkEvents(alertSid, eventIds);
            }
        } catch (Exception e) {
            logger.error("解绑失败：" + e.getMessage(), e);
            return JSONMapper.createReturnMap(SystemConstant.RESPONSE_CODE_FAILURE,"", null, null);
        }
        logger.debug("<<<<< 解绑事件 unLinkEvents() alertSid:" + alertSid + ", eventIds:" + eventIds);
        return JSONMapper.createReturnMap(SystemConstant.RESPONSE_CODE_SUCCESS, "", null, null);
    }

    /**
     * 获取单个告警通过sid
     */
    @ResponseBody
    @RequestMapping("/getAlertBySid")
    public ModelMap getAlertBySid(String alertSid) {
        logger.debug(">>>>> 获取单个告警 getAlertBySid() alertSid:" + alertSid);
        AlertVO alert = null;
        Long earliestUnix = 0L;
        Long latestUnix = 0L;
        try {
            alert = indexAlertService.getAlertsBySids(alertSid).get(0);
            earliestUnix = (DateFormatUtils.dateStr2TimeStamp(alert.getEarliestTime(), "yyyy/MM/dd:HH:mm:ss")) / 1000;
            latestUnix = (DateFormatUtils.dateStr2TimeStamp(alert.getLatestTime(), "yyyy/MM/dd:HH:mm:ss")) / 1000;
        } catch (Exception e) {
            logger.error("获取单个告警出错：" + e.getMessage(), e);
            return JSONMapper.createReturnMap(SystemConstant.RESPONSE_CODE_FAILURE, "", null, "");
        }
        ModelMap model = JSONMapper.createReturnMap(SystemConstant.RESPONSE_CODE_SUCCESS, "", null, alert);
        model.addAttribute("address", address);
        model.addAttribute("domain", domain);
        model.addAttribute("earliestUnix", earliestUnix);
        model.addAttribute("latestUnix", latestUnix);
        logger.debug("<<<<< 获取单个告警 getAlertBySid() alertSid:" + alertSid);
        return model;
    }

    /**
     * 从Splunk中触发的告警会触发同源处理
     * @param request
     */
    @RequestMapping(value = "/triggerAlert", method = RequestMethod.POST)
    public void triggerAlerts(HttpServletRequest request) {
        String line = "";
        StringBuilder sb = new StringBuilder();
        String alertSid = "";
        String alertName = "";
        try {
            //拼接告警数据
            while ((line = request.getReader().readLine()) != null) {
                sb.append(line);
            }
            JsonNode jsonNode = JSONUtils.jsonStr2JsonNode(sb.toString());
            alertSid = jsonNode.get("sid").asText();
            alertName = jsonNode.get("search_name").asText();
            JsonNode result = jsonNode.get("result");
            if (alertSid == null || alertSid == "") {
                logger.error("告警的sid为空，不进行存储！");
                return;
            }
            /**
             * 逻辑：
             * 1.对新告警进行同源处理（未做）
             *      如果新告警的结果和之前的告警结果相同，就将这个新告警添加到之前告警的绑定事件中去
             * 2.保存新告警（和事件）
             */
            // 同源
            String oldSid = indexEventService.dealSameSource(alertSid, alertName, result);


            /**
             * 由于在主线程中，查询不到Splunk新生成的告警，因此开启子线程查询
             * 出于安全角度考虑，要对子线程设置超时时间
             * 并且，这个超时时间设置同样不能在主线程操作，因此需要另外开辟一个单独的线程来设置超时时间
             */
            AlertRunnable runnable = new AlertRunnable(splunkBaseService.getService(), indexAlertService, alertSid, alertName, oldSid);
            // 开辟子线程保存新告警
            Future<?> future = taskExecutor.submit(runnable);
            // 开辟子线程设置线程超时时间
            taskExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        future.get(5000, TimeUnit.MILLISECONDS);
                    } catch (InterruptedException interrupted) {
                        logger.error("子线程被打断：" + interrupted.getMessage(), interrupted);
                        future.cancel(true);
                        // future.cancel() 不能打断线程的无限循环，因此设置循环标记
                        runnable.setAlive(false);
                    } catch (ExecutionException execute) {
                        logger.error("线程执行异常：" + execute.getMessage(), execute);
                        future.cancel(true);
                        runnable.setAlive(false);
                    } catch (TimeoutException timeout) {
                        logger.error("子线程超时：" + timeout.getMessage(), timeout);
                        future.cancel(true);
                        runnable.setAlive(false);
                    }
                }
            });
        } catch (Exception e) {
            logger.error("同源告警出错：" + e.getMessage(), e);
        }
    }

}
